//utils.cpp
//author: aandriyash

#ifndef UTILS_H_
#define UTILS_H_

#include <stdio.h>

#include <iostream>

#ifdef WIN32
    #include <Windows.h>
#else
    #include <sys/types.h>
    #include <sys/mman.h>
#endif

#ifdef WIN32
    #define Mapping     Mapping_WIN32
    #define Unmapping   Unmapping_WIN32
#else
    #define Mapping     Mapping_XNIX
    #define Unmapping   Unmapping_XNIX
#endif

#define RETURN_IF_ERROR(PARAM)\
    if(1 == PARAM)\
    {\
        return 1;\
    }\

#define RETURN_IF_ERROR_TRACE(PARAM, MESSAGE)\
    if(PARAM)\
    {\
        printf(MESSAGE);\
        return 1;\
    }\
    
struct RaidConfig 
{
    int totalDisks;
    int totalBlocks;
    int blockSize;
    int raidLevel;
    
    int Validate()
    {
        if ((totalDisks < 2)||(totalDisks > 5)) 
        {
            std::cout << "Number of disks must be 2..5\n";
            return 1;
        }

        if ((totalBlocks < 100) || (totalBlocks > 10000)) 
        {
            std::cout << "Number of blocks must be 100..10000\n";
            return 1;
        }

        if ((blockSize < 256) || (blockSize > 2048)) 
        {
            std::cout << "Size of block must be 256..2048\n";
            return 1;
        }

        if ((raidLevel < 0) || (raidLevel > 5) || (raidLevel == 2)) 
        {
            std::cout << "Level of RAID must be 0, 1, 3, 4 or 5\n";
            return 1;
        }

        if ((raidLevel >= 3) && (totalDisks < 3)) 
        {
            std::cout << "Not enough disks for chosen level of RAID\n";
            return 1;
        }
        
        return 0;
    }
    
    int Read() 
    {
        FILE* pConfig = fopen("config", "r");
        
        if (NULL == pConfig) 
        {
            std::cout << "Can't open config file.\n";
        
            return 1;
        }
        
        RaidConfig raidConf;    
        fscanf(pConfig, "%i\n%i\n%i\n%i\n", &totalDisks, &totalBlocks, &blockSize, &raidLevel);
        
        fclose(pConfig);
        
        return 0;
    }
    
    int Write()
    {
        FILE* pCONFIG_FILE = fopen("config", "w");

        if (NULL == pCONFIG_FILE) 
        {
            std::cout << "Can't open config file\n";
            return 1;
        }

        fprintf(pCONFIG_FILE, "%i\n", totalDisks);
        fprintf(pCONFIG_FILE, "%i\n", totalBlocks);
        fprintf(pCONFIG_FILE, "%i\n", blockSize);
        fprintf(pCONFIG_FILE, "%i\n", raidLevel);

        fclose(pCONFIG_FILE);
    }
};

bool IsDiskCrashed(
    int totalDisks, 
    bool rebuild
) 
{
    for (int i = 0; i < totalDisks; i++) 
    {
        char name[10];
        sprintf(name, "Disk%i", i + 1);
        FILE * pDISC_FILE = fopen(name,"rb+");
        if (NULL == pDISC_FILE) 
        {
            if (!rebuild) 
            {
                std::cout << "Can't open file "<< name <<"\n";
            }
            return true;
        }
        fclose(pDISC_FILE);
    }
    return false;
}

#ifdef WIN32
    char** ppMappedDataArray = new char*[5];
    HANDLE* pMappedFiles = new HANDLE[5];
    HANDLE* pMappings = new HANDLE[5];

    int Unmapping_WIN32(
        int totalDisks, 
        int totalBlocks, 
        int blockSize, 
        bool isRebuild
    )
    {
            for (int i = 1; i < totalDisks + 1; i++) 
            {
                
                if (( 0 == UnmapViewOfFile(ppMappedDataArray[i]) ) && ( !isRebuild ) ) 
                {
                    std::cout << "Unmapping disk " << i << "\n";
                    std::cout << "Unmapping error.\n";
                    return 1;
                }
                
                if (( 0 == CloseHandle(pMappings[i]) ) && ( !isRebuild )) 
                {
                    std::cout << "Unmapping disk " << i << "\n";
                    std::cout << "Closing mapping error\n";
                    return 1;
                }
                
                if (( 0 == CloseHandle(pMappedFiles[i]) ) && (!isRebuild)) 
                {
                    std::cout << "Unmapping disk " << i << "\n";
                    std::cout << "Closing mapped file error\n";
                    return 1;
                }
            }

            delete[] pMappedFiles;
            delete[] pMappings;
            delete[] ppMappedDataArray;

            return 0;
    }

    int Mapping_WIN32(
        int totalDisks, 
        int numOfBlocks, 
        int blockSize, 
        bool isRebuild
    ) 
    {
        for (int i = 1; i < totalDisks + 1; i++) 
        {
            char name[10];
            sprintf(name, "Disk%i", i);
            
            OFSTRUCT buff;
            HFILE mappedFileHandeler = OpenFile(name, &buff, OF_READWRITE);
            
            if ( (HFILE_ERROR == mappedFileHandeler) && (!isRebuild)) 
            {
                std::cout << "Can't open file " << name << "\n";
                return 1;
            }
            
            pMappedFiles[i] = (HANDLE)  (mappedFileHandeler);
            HANDLE mappingHandler 
                    = CreateFileMapping( 
                        (HANDLE)(mappedFileHandeler)
                        , NULL
                        , PAGE_READWRITE
                        , 0
                        , 0
                        , NULL
                    );
            
            if ( (NULL == mappingHandler) && (!isRebuild)) 
            {
                std::cout << "Mapping error.\n";
                return 1;
            }
            
            pMappings[i] = mappingHandler; 
            void* pMappedData = MapViewOfFile(mappingHandler, FILE_MAP_WRITE, 0, 0, 0);
           
            if ( (NULL == pMappedData) && (!isRebuild)) 
            {
                std::cout << "Mapping error.\n";
                return 1;
            }
            ppMappedDataArray[i] = static_cast<char*>(pMappedData);
        }
        return 0;
    }

#else //XNIX CASE

    char** ppMappedDataArray = new char*[5];
    FILE** ppDisks = new FILE*[5];
    
    int Unmapping_XNIX(
        int totalDisks, 
        int totalBlocks, 
        int blockSize, 
        bool isRebuild
    )
    {
        for (int i = 1; i < totalDisks + 1; i++) 
        {
    
            if (( -1 == munmap(ppMappedDataArray[i], totalBlocks * blockSize)) && (!isRebuild)) 
            {
                std::cout << "Unmapping error.\n";
            }
            fclose(ppDisks[i]);
        }
        
        delete[] ppDisks;
        delete[] ppMappedDataArray;
        
        return 0;
    }

    int Mapping_XNIX(
        int totalDisks, 
        int totalBlocks, 
        int blockSize, 
        bool isRebuild
    ) 
    {
        FILE * pDisk; //RAID disk

        for (int i = 1; i < totalDisks + 1; i++) 
        {
            char name[10];
            sprintf(name, "Disk%i", i);
            pDisk = fopen(name, "wb+");
            ppDisks[i] = pDisk;
            void* pMappedData = mmap( 0
                                    , totalBlocks * blockSize
                                    , PROT_WRITE|PROT_READ
                                    , MAP_SHARED
                                    , fileno(pDisk)
                                    , 0
                                );
            
            if (MAP_FAILED == pMappedData) 
            {
                if (isRebuild) 
                {
                    ppMappedDataArray[i] = NULL;
                    return 0;
                } 
                else 
                {
                    std::cout << "Mapping error. " << name << ".\n";
                    return 1;
                }
            }
            ppMappedDataArray[i] = static_cast<char*>(pMappedData);
        }
        return 0;
    }
#endif //#ifdef WIN32

#endif //UTILS_H_
